package net.aynt.core.mvc;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.aynt.blog.handler.SimpleExceptionHandler;
import net.aynt.blog.interceptor.CustomerInterceptor;
import net.aynt.core.db.DBManager;
import net.aynt.core.exception.ActionException;
import net.aynt.core.exception.NotFoundTemplateException;
import net.aynt.core.mvc.view.FreeMarkerView;
import net.aynt.core.mvc.util.AyntNumberUtils;
import net.aynt.core.mvc.util.AyntStringUtils;
import net.aynt.core.mvc.util.ClassUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DispatcherFilter implements Filter {

	private final static Logger log = LoggerFactory
			.getLogger(DispatcherFilter.class);
	
	private ServletContext context;
	
	private final static Map<String, PathView> templates = new HashMap<String, PathView>();
	private final static HashMap<String, Object> actions = new HashMap<String, Object>();
	private final static HashMap<String, Method> methods = new HashMap<String, Method>();
	
	private Interceptor Interceptor = new CustomerInterceptor();
	private ExceptionHandler exceptionHandler = new SimpleExceptionHandler();
	private View view = new FreeMarkerView();// 视图类型
	private final static String rootDomainTemplatePath = "/WEB-INF/www";// 模板根路径

	public void init(FilterConfig cfg) throws ServletException {
		log.info("DispatcherFilter初始化");
		this.context = cfg.getServletContext();
		
		// 设置上传文件尺寸
		String tmpSzie = cfg.getInitParameter("uploadFileMaxSize");
		if (AyntNumberUtils.isNumber(tmpSzie)) {
			WebContext.setMaxSize(AyntNumberUtils.toInt(tmpSzie));
		}
		
		String SystemInitProperties = cfg.getInitParameter("systemInit");
		if(!AyntStringUtils.isEmpty(SystemInitProperties)){
			try {
				SystemInit systemInit = (SystemInit) Class.forName(SystemInitProperties)
				.newInstance();
				systemInit.init(context);
			} catch (Exception e) {
				log.error("systemInit error:", e);
			}
		}
		
		String actionPackage = cfg.getInitParameter("action");
		if(AyntStringUtils.isEmpty(actionPackage)){
			log.error("action is empty");
			System.exit(0);
		}

		initActions(actionPackage);
	}

	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		WebContext rc = WebContext.begin(this.context, request, response);	
		String reqURI = rc.getURI();
		log.info("-->reqURI="+reqURI);
		if (AyntStringUtils.endsWithIgnoreCase(reqURI, "ico")) {
			chain.doFilter(request, response);
			return;
		}
		try {
			process(rc, reqURI);
		} catch (Exception e) {
			try {
				handleMethodReturn(rc, exceptionHandler.handle(rc, e));
			} catch (Exception e1) {
				throw new ServletException(e1);
			}
		} finally {
			if (rc != null)
				rc.end();
			DBManager.closeConnection();
		}
	}

	/**
	 * 
	 * @see javax.servlet.Filter#destroy()
	 */
	public void destroy() {
		for (Object action : actions.values()) {
			try {
				Method dm = action.getClass().getMethod(
						AyntConfig.ACTION_DESTROY_METHOD);
				dm.invoke(action);
			} catch (NoSuchMethodException e) {
			} catch (Exception e) {
				log.error("Destroy action is error: "
						+ action.getClass().getName(), e);
			}
		}
	}

	/**
	 * 逻辑处理
	 * 
	 * @param rc
	 * @param requestURI
	 * @throws Exception
	 */
	protected void process(WebContext rc, String requestURI) throws Exception {
		// 类似 /action/ admin/home/index
		String selfURI = checkURI(requestURI);
		ActionObject actionObject =  _LoadAction(selfURI);
		if (actionObject == null) {
			if (log.isDebugEnabled()) {
				log.debug("requestURI=" + requestURI + "--->not found action");
			}
			rc.notFound();
			return;
		}
		// 加载Action类
		Object action = actionObject.getAction();

		String[] parts = AyntStringUtils.split(selfURI, '/');
		int actionLength = AyntStringUtils.split(actionObject.getName(), '/').length;
		String actionMethodName = AyntConfig.ACTION_DEFAULT_METHOD;
		// 带路径参数
		if (parts.length > actionLength
				&& !AyntNumberUtils.isDigits(parts[actionLength])) {
			actionMethodName = parts[actionLength];
			actionLength++;
		}

		// 查找methodOfAction
		Method methodOfAction = getActionMethod(action, actionMethodName);
		if (methodOfAction == null){
			if (log.isDebugEnabled()) {
				log.debug("requestURI=" + requestURI
						+ "--->not found method Of Action");
			}
			rc.notFound();
			return;
		}
		log.info("-->selfURI="+selfURI+";action=action [action="+action+", actionMethodName="+methodOfAction.getName()+"]");
		// 路径参数
		List<String> args = new ArrayList<String>();
		for (int i = actionLength; i < parts.length; i++) {
			if (AyntStringUtils.isBlank(parts[i]))
				continue;
			args.add(parts[i]);
		}
		Exception exception = null;
		try {
			//代替拦截器###########################################################
			Interceptor.preHandle(rc,methodOfAction);
			//代替拦截器	
			// 调用Action方法之准备参数，不能重载
			int argLength = methodOfAction.getParameterTypes().length;
			Object returnValue = null;
			switch (argLength) {
			case 0: // login()
				returnValue = methodOfAction.invoke(action);
				break;
			case 1:
				returnValue = methodOfAction.invoke(action, rc);
				break;
			case 2: // login(rc, String[] extParams) or get(rc, long id);
				boolean isLong = methodOfAction.getParameterTypes()[1]
						.equals(Long.class);
				returnValue = methodOfAction.invoke(
						action,
						rc,
						isLong ? AyntNumberUtils.toLong(args.get(0), 1L) : args
								.toArray(new String[args.size()]));
				break;
			default:
				String message = "requestURI=" + requestURI
						+ "--->not found args matach method Of Action";
				throw new ActionException(message);
			}
			
			handleMethodReturn(rc, returnValue);
			if (log.isDebugEnabled()) {
				log.debug("requestURI="
						+ requestURI
						+ ";action="
						+ action
						+ ";method="
						+ actionMethodName
						+ ";result="
						+ (returnValue != null ? returnValue.toString()
								: "NULL") + ";args=" + args);
			}
		} catch (InvocationTargetException e) {
			exception = new Exception(e.getCause());
			throw exception;
		} catch (Exception e) {
			exception = e;
			throw exception;
		} 
	}

	/**
	 * 处理返回值
	 * 
	 * @param rc
	 * @param result
	 * @throws Exception
	 */
	private void handleMethodReturn(WebContext rc, Object result)
			throws Exception {
		if (result instanceof String) {
			String returnURI = (String) result;
			if (returnURI.startsWith(AyntConfig.ACTION_REDIRECT)) {
				rc.redirect(AyntStringUtils.substringAfter(returnURI,
						AyntConfig.ACTION_REDIRECT));
			} else {
				String[] paths = AyntStringUtils.split(returnURI, '/');
				PathView pathView = getTemplate(paths,
						paths.length);
				if (pathView == null) {
					throw NotFoundTemplateException.build(returnURI, view);
				}
				pathView.getView().render(rc, pathView.getTemplatePath());
				if (log.isInfoEnabled()) {
					log.info("-->requestURI=" + returnURI + ";pathView="
							+ pathView);
				}
			}
		}
	}

	/**
	 * 默认跳转到首页的匹配模式
	 */
	private final static Pattern HOME_PATTERN = Pattern.compile("^/\\d.*");

	/**
	 * 检查uri，返回适当的uri
	 * 
	 * @param uri
	 * @return uri
	 */
	private String checkURI(String uri) {
		Matcher matcher = HOME_PATTERN.matcher(uri);
		if (uri.equals("/") || matcher.matches()) {
			uri = "/" + AyntConfig.ACTION_DEFAULT + "/"
					+ AyntConfig.ACTION_DEFAULT_METHOD + uri;
		}
		return uri;
	}

	private PathView getTemplate(String[] paths,int idx_base) {
		StringBuilder template = new StringBuilder(rootDomainTemplatePath);//域名模板
		String the_path = null;
		if (idx_base == 0) {// 返回默认页面
			the_path = template.toString() + "/" + AyntConfig.ACTION_DEFAULT_METHOD;
		} else {
			for (int i = 0; i < idx_base; i++) {
				template.append('/');
				template.append(paths[i]);
			}
			the_path = template.toString();
		}
		PathView pathView = queryFromCache(the_path);
		if (pathView != null) {
			pathView.setTemplatePath(the_path + pathView.getView().getExt());
			return pathView;
		}
		if (pathView == null && idx_base > 0) {
			return getTemplate(paths, idx_base - 1);
		}

		return pathView;
	}

	/**
	 * 查询某个页面是否存在，如果存在则缓存此结果，并返回
	 * 
	 * @param path
	 * @return
	 */
	private PathView queryFromCache(String path) {
		PathView pathView = templates.get(path);
		if (pathView == null){
			String pathAndExt = path + view.getExt();
			File testFile = new File(context.getRealPath(pathAndExt));
			boolean isExists = testFile.exists() && testFile.isFile();
			if (isExists) {
				pathView = new PathView(path, view);
				templates.put(path, pathView);
			}
		}
		return pathView;
	}
	
	/**
	 * 初始化所有的Action
	 */
	private void initActions(String actionPackages) {
		Map<String, String> actionClassNames = ClassUtils.autoScanPackage(actionPackages, true);
		Set<Entry<String, String>> set = actionClassNames.entrySet();
		for (Entry<String, String> entry : set) {
			Object action = loadActionOfFullName(entry.getValue());
			actions.put(entry.getKey().toLowerCase(), action);// Action加入缓存
			for (Method m : action.getClass().getMethods()) {
				String key = action.getClass().getName() + '$'
						+ m.getName().toLowerCase();
				if (m.getModifiers() == Modifier.PUBLIC &&
						!(m.getName().equals("equals") 
								|| m.getName().equals("toString"))) {
						methods.put(key, m);
				}
			}
		}
	}
	
	/**
	 * 加载Action类
	 * @param actName
	 * @return ActionObject
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 */
	protected ActionObject _LoadAction(String actName) throws InstantiationException,IllegalAccessException {
		if (actName.endsWith("/")) {
			actName = actName.substring(0, actName.lastIndexOf("/"));
		}
		Object action = actions.get(actName.toLowerCase());
		if(null!=action) 
			return new ActionObject(actName, action);
		if (action == null && actName.startsWith("/")) {
			actName = AyntStringUtils.substringBeforeLast(actName, "/");
			return _LoadAction(actName);
		}else{
			return null;
		}
		
	}
	
	/**
	 * 创建Action实例
	 * 
	 * @param cls
	 * @return
	 */
	private Object loadActionOfFullName(String cls) {
		Object action = null;
		try {
			action = Class.forName(cls).newInstance();
			try {
				Method actionInitMethod = action.getClass().getMethod(
						AyntConfig.ACTION_INIT_METHOD);// 初始化方法
				actionInitMethod.invoke(action);
			} catch (Exception e) {
			}
		} catch (Exception excp) {
			log.warn("加载action出错！class=" + cls);
		}

		return action;
	}

	/**
	 * 获取名为{methodName}的方法，假如没有找到，则试着查找{"post" + methodName}方法
	 * 
	 * @param action
	 * @param methodName
	 * @return
	 */
	private Method getActionMethod(Object action, String methodName) {
		if (methodName.equals(AyntConfig.ACTION_INIT_METHOD) ||  		// 不能直接调用init和destroy方法
				methodName.equals(AyntConfig.ACTION_DESTROY_METHOD)) return null;
		String key = action.getClass().getName() + '$';
		methodName = methodName.toLowerCase();
		Method method = methods.get(key + methodName);
		if (method != null) return method;
		method = methods.get(key + "post" + methodName);
		if (method != null) return method;
		method = methods.get(key + "index");
		return method;
	}

}
